package b649a01.runners;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.Integer;
import java.lang.NumberFormatException;
import java.nio.ByteBuffer;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.LinkedList;
import java.util.Hashtable;

/***
 * 
 * An interpreter for parsing the TRITE code.
 * 
 * @author Chao Sun
 * @version 1.0
 * 
 */
public class interpreter {
	private byte[] dumpArray;
	private int lineNum;
	private String[] lineArray;
	private int[] R;
	private double[] F;
	private int branchIndex = 0;
	private Hashtable<String, Integer> branchTable;

	/**
	 * Constructor. Initialize some of the fields.
	 */
	public interpreter() {
		dumpArray = new byte[1048576]; // 1MB of dump space
		R = new int[16]; // 16 integer registers
		R[0] = 0;
		F = new double[16]; // 16 floating point registers
		branchTable = new Hashtable<String, Integer>();
	}

	/**
	 * Read a file and interpret every line.
	 * 
	 * @param args
	 *          the command line string array, containing the name of the code
	 *          file to be interpreted and the name of the memory dump file
	 * @throws IOException
	 */
	private void run(String[] args) throws IOException {
		String fileName = args[0];
		String memDump = args[1];

		// read memory dump file into an array
		BufferedReader dump = new BufferedReader(new FileReader(memDump));
		for (int i = 0; i < dumpArray.length; i++) {
			dumpArray[i] = (byte) dump.read();
		}

		// read lines in file to a linked list
		BufferedReader in = new BufferedReader(new FileReader(fileName));
		String line;
		LinkedList<String> lineList = new LinkedList<String>();
		while ((line = in.readLine()) != null) {
			lineList.add(line);
		}
		lineNum = lineList.size();
		lineArray = new String[lineNum];

		// read the instruction parts into a string array
		for (int i = 0; i < lineNum; i++) {
			StringTokenizer lineTokenizer = new StringTokenizer(lineList.poll(), ";");
			String instruction = lineTokenizer.nextToken();
			lineArray[i] = instruction;
		}

		for (int i = 0; i < lineNum; i++) {
			String instruction = lineArray[i];

			// break the instruction into operation and operand
			StringTokenizer insTokenizer = new StringTokenizer(instruction);
			String first = null;
			try {
				first = insTokenizer.nextToken();
			} catch (NoSuchElementException e) {
				continue;
			}
			String operation = null;

			// assign operation and operand
			if (first.endsWith(":")) { // this is a branch
				// maintain the branch index
				branchTable.put(first.substring(0, first.length() - 1), i);
				operation = insTokenizer.nextToken();
			} else { // other instruction
				operation = first;
			}
			String operand = insTokenizer.nextToken();
			StringTokenizer opTokenizer = new StringTokenizer(operand, ",");

			// simulate running the instruction
			int branchStatus = simulate(operation, opTokenizer);
			if (branchStatus == 1) { // branch is taken. branch index is assigned
				i = branchIndex - 1; // move to the start of the branch
			} else if (branchStatus == -1) {
				System.exit(1); // exit program on unknown operator
			}
		}

		printResult();
	}

	/**
	 * Print out the calculation result.
	 */
	private void printResult() {
		int index = 1;
		for (int j = 0; j < 300; j++) { // floating point
			if (j % 10 == 0) {
				System.out.println("");
			}
			if (j % 100 == 0) {
				System.out.println("\nMatrix" + index++ + ":\n");
			}
			ByteBuffer buffer = ByteBuffer.allocate(8);
			buffer.put(dumpArray, j * 8, 8);
			System.out.print(buffer.getDouble(0) + "\t");
		}
		System.out.println("\n");
	}

	/**
	 * Simulate an instruction
	 * 
	 * @param operation
	 *          the name of the operation in this instruction
	 * @param opTokenizer
	 *          the tokenizer for breaking the operands
	 * @return an integer value indicating if a branch is taken
	 */
	private int simulate(String operation, StringTokenizer opTokenizer) {
		if (operation.equals("NOOP")) { // Do nothing
			return 0;
		}

		if (operation.equals("ADD")) { // Integer add
			int tIndex = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int s1Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int s2Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			R[tIndex] = R[s1Index] + R[s2Index];
			return 0;
		}

		if (operation.equals("SUB")) { // Integer subtract
			int tIndex = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int s1Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int s2Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			R[tIndex] = R[s1Index] - R[s2Index];
			return 0;
		}

		if (operation.equals("MUL")) { // Integer multiply
			int tIndex = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int s1Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int s2Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			R[tIndex] = R[s1Index] * R[s2Index];
			return 0;
		}

		if (operation.equals("DIV")) { // Integer divide
			int tIndex = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int s1Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int s2Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			R[tIndex] = R[s1Index] / R[s2Index];
			return 0;
		}

		if (operation.equals("L")) { // Integer load
			int tIndex = Integer.parseInt(opTokenizer.nextToken().substring(1));
			String s = opTokenizer.nextToken();
			StringTokenizer sTokenizer = new StringTokenizer(s, "(");
			int offset = Integer.parseInt(sTokenizer.nextToken());
			String register = sTokenizer.nextToken();
			if (!register.startsWith("R")) {
				System.err.println("Address must be stored in an integer register");
				System.exit(1);
			}
			int registerNum = Integer.parseInt(register.substring(1, register
					.length() - 1));
			int base = R[registerNum] + offset;
			ByteBuffer buffer = ByteBuffer.allocate(4);
			buffer.put(dumpArray, base, 4);
			R[tIndex] = buffer.getInt(0);
			return 0;
		}

		if (operation.equals("LI")) { // Integer load immediate
			int tIndex = Integer.parseInt(opTokenizer.nextToken().substring(1));
			R[tIndex] = Integer.parseInt(opTokenizer.nextToken());
			return 0;
		}

		if (operation.equals("S")) { // Integer store
			int s1Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			String s2 = opTokenizer.nextToken();
			StringTokenizer s2Tokenizer = new StringTokenizer(s2, "(");
			int offset = Integer.parseInt(s2Tokenizer.nextToken());
			String register = s2Tokenizer.nextToken();
			if (!register.startsWith("R")) {
				System.err.println("Address must be stored in an integer register");
				System.exit(1);
			}
			int registerNum = Integer.parseInt(register.substring(1, register
					.length() - 1));
			int base = R[registerNum] + offset;
			ByteBuffer buffer = ByteBuffer.allocate(4);
			buffer.putInt(R[s1Index]);
			byte buf[] = buffer.array();
			for (int i = 0; i < 4; i++) {
				dumpArray[base + i] = buf[i];
			}
			return 0;
		}

		if (operation.equals("BEQ")) { // Branch if equal
			int s1Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int s2Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			if (R[s1Index] == R[s2Index]) {
				branch(opTokenizer.nextToken());
				return 1;
			} else {
				return 0;
			}
		}

		if (operation.equals("BNEQ")) { // Branch if not equal
			int s1Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int s2Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			if (R[s1Index] != R[s2Index]) {
				branch(opTokenizer.nextToken());
				return 1;
			} else {
				return 0;
			}
		}

		if (operation.equals("BGTZ")) { // Branch if greater than zero
			int sIndex = Integer.parseInt(opTokenizer.nextToken().substring(1));
			if (R[sIndex] > 0) {
				branch(opTokenizer.nextToken());
				return 1;
			} else {
				return 0;
			}
		}

		if (operation.equals("BLTZ")) { // Branch if less than zero
			int sIndex = Integer.parseInt(opTokenizer.nextToken().substring(1));
			if (R[sIndex] < 0) {
				branch(opTokenizer.nextToken());
				return 1;
			} else {
				return 0;
			}
		}

		if (operation.equals("MOVE")) {
			// Move an integer register into a floating point register
			int sIndex = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int tIndex = Integer.parseInt(opTokenizer.nextToken().substring(1));
			F[tIndex] = (double) R[sIndex];
			return 0;
		}

		if (operation.equals("ADD.D")) { // Floating point add
			int tIndex = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int s1Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int s2Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			F[tIndex] = F[s1Index] + F[s2Index];
			return 0;
		}

		if (operation.equals("SUB.D")) { // Floating point subtract
			int tIndex = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int s1Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int s2Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			F[tIndex] = F[s1Index] - F[s2Index];
			return 0;
		}

		if (operation.equals("MUL.D")) { // Floating point multiply
			int tIndex = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int s1Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int s2Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			F[tIndex] = F[s1Index] * F[s2Index];
			return 0;
		}

		if (operation.equals("DIV.D")) { // Floating point divide
			int tIndex = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int s1Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int s2Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			F[tIndex] = F[s1Index] / F[s2Index];
			return 0;
		}

		if (operation.equals("L.D")) { // Floating point load
			int tIndex = Integer.parseInt(opTokenizer.nextToken().substring(1));
			String s = opTokenizer.nextToken();
			StringTokenizer sTokenizer = new StringTokenizer(s, "(");
			int offset = Integer.parseInt(sTokenizer.nextToken());
			String register = sTokenizer.nextToken();
			if (!register.startsWith("R")) {
				System.err.println("Address must be stored in an integer register");
				System.exit(1);
			}
			int registerNum = Integer.parseInt(register.substring(1, register
					.length() - 1));
			int base = R[registerNum] + offset;
			ByteBuffer buffer = ByteBuffer.allocate(8);
			buffer.put(dumpArray, base, 8);
			F[tIndex] = buffer.getDouble(0);
			return 0;
		}

		if (operation.equals("S.D")) { // Floating point store
			int s1Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			String s2 = opTokenizer.nextToken();
			StringTokenizer s2Tokenizer = new StringTokenizer(s2, "(");
			int offset = Integer.parseInt(s2Tokenizer.nextToken());
			String register = s2Tokenizer.nextToken();
			if (!register.startsWith("R")) {
				System.err.println("Address must be stored in an integer register");
				System.exit(1);
			}
			int registerNum = Integer.parseInt(register.substring(1, register
					.length() - 1));
			int base = R[registerNum] + offset;
			ByteBuffer buffer = ByteBuffer.allocate(8);
			buffer.putDouble(F[s1Index]);
			byte buf[] = buffer.array();
			for (int i = 0; i < 8; i++) {
				dumpArray[base + i] = buf[i];
			}
			return 0;
		}

		if (operation.equals("BEQ.D")) { // Branch if equal
			int s1Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int s2Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			if (F[s1Index] == F[s2Index]) {
				branch(opTokenizer.nextToken());
				return 1;
			} else {
				return 0;
			}
		}

		if (operation.equals("BNEQ.D")) { // Branch if not equal
			int s1Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int s2Index = Integer.parseInt(opTokenizer.nextToken().substring(1));
			if (F[s1Index] != F[s2Index]) {
				branch(opTokenizer.nextToken());
				return 1;
			} else {
				return 0;
			}
		}

		if (operation.equals("BGTZ.D")) { // Branch if greater than zero
			int sIndex = Integer.parseInt(opTokenizer.nextToken().substring(1));
			if (F[sIndex] > 0) {
				branch(opTokenizer.nextToken());
				return 1;
			} else {
				return 0;
			}
		}

		if (operation.equals("BLTZ.D")) { // Branch if less than zero
			int sIndex = Integer.parseInt(opTokenizer.nextToken().substring(1));
			if (F[sIndex] < 0) {
				branch(opTokenizer.nextToken());
				return 1;
			} else {
				return 0;
			}
		}

		if (operation.equals("MOVE.D")) {
			// Move a floating point register into an integer register
			int sIndex = Integer.parseInt(opTokenizer.nextToken().substring(1));
			int tIndex = Integer.parseInt(opTokenizer.nextToken().substring(1));
			R[tIndex] = (int) F[sIndex];
			return 0;
		}

		System.err.println("Unknown operator! Exiting ...");
		return -1;
	}

	/**
	 * Take a branch. Modify the branch index. Add branch label to hash table if
	 * needed.
	 * 
	 * @param br
	 *          the label or the offset of the branch
	 */
	private void branch(String br) {
		if (branchTable.containsKey(br)) { // branch already in table
			branchIndex = branchTable.get(br);
			return;
		}

		for (int i = 0; i < lineNum; i++) { // branch not in table, try to find it
			String instruction = lineArray[i];
			StringTokenizer insTokenizer = new StringTokenizer(instruction);
			String first = insTokenizer.nextToken();
			if (first.endsWith(":")) {
				String bra = first.substring(0, first.length() - 1);
				if (bra.equals(br)) {
					branchTable.put(bra, i);
					branchIndex = i;
					return;
				}
			}
		}

		try { // immediate number as branch target
			branchIndex = (int) (Integer.parseInt(br) / 32);
		} catch (NumberFormatException e) { // illegal branch label
			System.err.println("Branch label error!");
			System.exit(1);
		}
		return;
	}

	/**
	 * main method for testing.
	 * 
	 * @param args
	 *          the command line string array
	 */
	public static void main(String args[]) {
		if (args.length != 2) {
			System.err.println("Args error! Usage: "
					+ "java interpreter <TRITE> <INIT_MEM>");
			System.exit(1);
		}

		try {
			interpreter Interpreter = new interpreter();
			Interpreter.run(args);
		} catch (FileNotFoundException e) {
			System.err.println("File not found! Exiting ...");
			e.printStackTrace();
		} catch (IOException e) {
			System.err.println("File reading error! Exiting ...");
			e.printStackTrace();
		}
	}

}